package com.github.tommas.admintpl.mybatis;

import com.github.tommas.admintpl.bean.model.BaseModel;
import com.github.tommas.admintpl.common.ServiceException;
import com.github.tommas.admintpl.security.UserPrincipal;
import com.github.tommas.admintpl.security.permission.restriction.Restriction;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.lang.util.Assert;
import org.apache.shiro.subject.Subject;

import java.util.*;

public interface Context {
    String[] getReturnColumns();

    // Getters used by common sql mappers
    String getCreatedBy();
    Date getCreatedAt();
    String getUpdatedBy();
    Date getUpdatedAt();
    String getDeletedBy();
    Date getDeletedAt();
    Integer getVersion();
    Map<String, Restriction> getRestriction();

    static DefaultContext insertContext(BaseModel model) {
        Subject subject = SecurityUtils.getSubject();
        UserPrincipal principal = UserPrincipal.fromPrincipalCollection(subject.getPrincipals());

        model.setCreatedBy(principal.getUsername());
        model.setCreatedAt(new Date());
        model.setVersion(1);

        return new DefaultContext(model, "id");
    }

    static DefaultContext updateContext(BaseModel model) {
        return updateContext(model, false);
    }

    static DefaultContext updateContext(BaseModel model, boolean skipVersion) {
        Subject subject = SecurityUtils.getSubject();
        UserPrincipal principal = UserPrincipal.fromPrincipalCollection(subject.getPrincipals());

        model.setUpdatedBy(principal.getUsername());
        model.setUpdatedAt(new Date());

        if (!skipVersion && model.getVersion() == null) {
            throw new ServiceException("Data version is required", null);
        }

        return new DefaultContext(model, "version");
    }

    static DefaultContext deleteContext() {
        Subject subject = SecurityUtils.getSubject();
        UserPrincipal principal = UserPrincipal.fromPrincipalCollection(subject.getPrincipals());

        BaseModel model = new BaseModel() {};
        model.setDeletedBy(principal.getUsername());
        model.setDeletedAt(new Date());

        return new DefaultContext(model);
    }

    @SuppressWarnings("unchecked")
    static DefaultContext restrictionContext(Restriction ...restrictions) {
        Map<String, Restriction> rmap = new HashMap<>(restrictions.length);
        for (Restriction restriction : restrictions) {
            if (restriction != null) {
                rmap.put(restriction.getName(), restriction);
            }
        }
        return new DefaultContext(!rmap.isEmpty() ? rmap : Collections.EMPTY_MAP);
    }

    /**
     * DefaultContext
     * Context used for
     * 1. Setting returned insert id to model
     * 2. Setting updated version value to model
     * 3. Providing logic deletion info
     */
    class DefaultContext implements Context {
        final BaseModel model;
        final String[] columns;
        final Set<String> columnSet;
        final Map<String, Restriction> restrictions;

        @SuppressWarnings("unchecked")
        public DefaultContext(BaseModel model, String ...columns) {
            this(model, Collections.EMPTY_MAP, columns);
        }

        public DefaultContext(BaseModel model, Map<String, Restriction> restrictions, String ...columns) {
            this.model = model;
            this.columns = columns;
            this.restrictions = restrictions;

            Set<String> s = new HashSet<>(columns.length);
            Collections.addAll(s, columns);
            columnSet = s;
        }

        public DefaultContext(Map<String, Restriction> restrictions) {
            Assert.notNull(restrictions);
            this.restrictions = restrictions;
            this.model = null;
            this.columns = null;
            this.columnSet = null;
        }

        @Override
        public String[] getReturnColumns() {
            return columns;
        }

        // BaseModel fields setters
        public void setId(Integer id) {
            if (columnSet.contains("id")) {
                model.setId(id);
            }
        }
        public void setVersion(Integer version) {
            if (columnSet.contains("version")) {
                model.setVersion(version);
            }
        }

        @Override
        public String getCreatedBy() {
            return model.getCreatedBy();
        }

        @Override
        public Date getCreatedAt() {
            return model.getCreatedAt();
        }

        @Override
        public String getUpdatedBy() {
            return model.getUpdatedBy();
        }

        @Override
        public Date getUpdatedAt() {
            return model.getCreatedAt();
        }

        public String getDeletedBy() {
            return model.getDeletedBy();
        }

        public Date getDeletedAt() {
            return model.getDeletedAt();
        }

        @Override
        public Integer getVersion() {
            return model.getVersion();
        }

        @Override
        public Map<String, Restriction> getRestriction() {
            return restrictions;
        }
    }
}
